#include "HttpParser.h"
#include "Socket.h"
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <string>
#include <string.h>
#include <sstream>
#include <fstream>
#include <vector>

using namespace std;

const int RCVBUFSIZE = 32;

// TCP client handling function
void HttpRequest::ParseSocket(TCPSocket *sock) {
    std::stringstream ss;

    char buffer[RCVBUFSIZE];
    int recvSize;

    int hdrFinish = 0; //State of a finite automaton to find an empty line

    while (hdrFinish != 4 && (recvSize = sock->recv(buffer, RCVBUFSIZE - 1)) > 0) { 
        buffer[recvSize] = 0;
        ss << buffer << endl;
        
        //Try to determine if the header has finished.
        for(int i = 0; i < recvSize; i++){
            if (hdrFinish == 0 && buffer[i] == '\r') {
                hdrFinish = 1;
            }
            else if (hdrFinish == 0 && buffer[i] == '\n') {
                //Try to identify \n\n in the stream
                hdrFinish = 3;
            }
            else if (hdrFinish == 1 && buffer[i] == '\n') {
                hdrFinish = 2;
            }
            else if (hdrFinish == 2 && buffer[i] == '\r') {
                hdrFinish = 3;
            }
            else if (hdrFinish == 3 && buffer[i] == '\n') {
                //Found \r\n\r\n in the stream, found the header.
                hdrFinish = 4;
            }
            else {
                hdrFinish = 0;
            }
        }
    }
    std::string method;
    if (ss >> method) {
        if (method == "GET") {
            this->method = method;
            std::string url;
            if (ss >> url) {
                this->url = DecodeURL(url);
            }
        }
        else {
            throw HttpError(501);
        }
    }

}

std::string HttpRequest::DecodeURL(const std::string & url) {
    std::string result;
    bool found = false;
    #define checkx(c) ((c>='0' && c<='9') || (c>='A' && c<='F') || (c>='a' && c<='f'))
    #define xtod(c) ((c>='0' && c<='9') ? c-'0' : ((c>='A' && c<='F') ? \
                c-'A'+10 : ((c>='a' && c<='f') ? c-'a'+10 : 0)))

    for(int i = 0; i < url.length(); i++){
        if(url[i]!='%') {
            result += url[i];
        }
        else {
            if(!(i+2 < url.length() && checkx(url[i+1]) && checkx(url[i+2])) )
                throw HttpError(400);
            result += (char)(xtod(url[i+1])*16 + xtod(url[i+2]));
            i+=2;
        }
    }
    return result;
}

bool CheckPathRestriction(const std::string & url) {
    int lastSlashPos = -1;
    int depth = 0;
    
    for(int i = 0; i < url.length(); i++) {
        if(url[i] == '\\' || url[i] == '/') {
            if(lastSlashPos == i-1 || (lastSlashPos == i-2 && url[i-1]=='.')) {
                depth; //Doesn't change anything
            }
            else if(lastSlashPos == i-3 && url[i-1]=='.' && url[i-2]=='.') {
                depth--;
            } else {
                depth++;
            }
            lastSlashPos = i;
        }
    }
    return depth>=0;
}

std::string MergePath(const std::string & base, const std::string & relative) {
    std::string result = base;
    if(!base.empty() && base[base.length()-1]!='/') {
        result += '/';
    }
    int i = 0;
    if(relative[0] == '/' || relative[0] == '\\') 
        i = 1;
    for(; i < relative.length(); i++) {
        if(relative[i] == '\\') {
            result += '/';
        }
        else {
            result += relative[i];
        }
    }
    return result;
}

void SendHttpResponse(const string & path, const HttpRequest & req, TCPSocket *sock) {
    ifstream file; 
    unsigned long long length;
    
    string url = req.getUrl();
    if (url == "")
        throw HttpError(400);
        
    try {
        if (!CheckPathRestriction(url)) 
            throw HttpError(404);
        
        file.open(MergePath(path,url).c_str(), ios::binary);
        if(!file.is_open())
            throw HttpError(404);
            
        file.seekg(0, ios::end);
        length = file.tellg();
        file.seekg(0, ios::beg);
    }
    catch (...) {
        throw HttpError(404);
    }

    char response[]="HTTP/1.0 200 OK\r\nServer: QB-Server\r\nContent-Type: text/html\r\nContent-Length: ";
    sock->send(response, strlen(response));
        
    stringstream ss;
    std::string str;
    ss << length;
    ss >> str;
    sock->sendString(str);
    
    sock->send("\r\n\r\n", 4);
    
    char * fileContent = new char[length];
    file.read(fileContent, length);
    sock->send(fileContent, length);
    delete [] fileContent;
}

void SendHttpErrorResponse(const HttpError & err, TCPSocket *sock)
{
    char response[]="HTTP/1.0 ";
    sock->send(response, strlen(response));
        
    stringstream ss;
    std::string str;
    ss << err.getErr();
    ss >> str;
    sock->sendString(str);
    
    char response2[] = " Error\r\nServer: QB-Server\r\nContent-Length: 148\r\nContent-Type: text/html\r\n\r\n<html>\n\n<head><title>Error</title></head>\n\n<body bgcolor=\"white\">\n\n<center><h1>Error</h1></center>\n\n<hr><center>QB-Server</center>\n\n</body>\n\n</html>";
    
    sock->send(response2, strlen(response2));

}
